<route lang="json">
  {
    "style": { 
      "navigationStyle": "custom",
      "enablePullDownRefresh": false,
      "app-plus": {
        "softinputMode": "adjustResize",
        "bounce": "none"
      }
    }
  }
</route>
<template>
  <view class="wrapper">
    <wd-navbar
      :title="navbarTitle"
      :bordered="false"
      :zIndex="60"
      :custom-style="`left:${sidebarTranslateX}px`"
      @click-left="handleToggleSidebar" 
      @click-right="handleNewConversation"
      fixed 
      placeholder 
      safeAreaInsetTop
    >
      <template #left>
        <wd-icon name="format-vertical-align-left" size="22px"></wd-icon>
      </template>
      <template #right>
        <view class="icon-chat-add !text-22px"></view>
      </template>
    </wd-navbar>
    <wd-sidebar
      :style="sidebarStyle"
      @change="handleSelectConversation"
    >
      <scroll-view  
        scroll-y
        class="list" 
        id="conversationScrollList"
        @scrolltolower="() => loadConversationsData()"
      >
        <view v-for="(item, index) in formatConversateData" :key="index" class="item-group">
          <view class="item-date">{{item.timeRange}}</view>
          <view
            v-for="citem in item.items" :key="citem.id"
            class="wd-sidebar-item"
            :class="context.conversationId == citem.id ? 'active' : ''"
          >
            <view class="name" @click="handleSelectConversation(citem)">{{citem.name}}</view>
            <view class="tool"> 
              <wd-icon name="delete1" style="margin-right: 8px; color: #fa4350;" @click="handleDeleteConversation(citem)"></wd-icon>
              <wd-icon name="edit-1" @click="handleRenameConversation(citem)"></wd-icon>
            </view>
            <view class="sw"></view>
          </view>
        </view>
      </scroll-view >
    </wd-sidebar>
    <view
      ref="messageContainer"
      class="message"
      :style="{left:sidebarTranslateX+'px'}"
    >
      <scroll-view 
        scroll-y
        class="list" 
        id="messageScrollList"
        :scroll-top="messageScrollTop"
        @scroll="handleMessageScroll"
        @scrolltoupper="() => loadMessagesData()"
      >
        <template v-if="messageLoading">
          <view style="display: flex; margin-bottom: 16px;" v-for="index in [1,2,3,4]" :key="index">
            <wd-skeleton :row-col="[{ size: '36px', type: 'circle' }]" />
            <wd-skeleton 
              animation="gradient"
              :custom-style="{ width: '100%', marginLeft: '12px',}" 
              :row-col="[1, 1, 1, 1, { width: '60%' }]" />
          </view>
        </template>
        
        <view class="item" v-for="(item, index) in messageDataList" :key="item.id">
          <view class="query" v-if="item.query">
            <view class="tool">
              <view 
                class="i-iconamoon-copy" 
                style="font-size: 22px;margin-right: 12px;" 
                @click="handleClickMessageTool(item.message_id, 'query_copy')"
              >
              </view>
            </view>
            <view class="content">
              <ua-markdown :source="item.query" />
            </view>
            <wd-img
              :width="35"
              :height="35" 
              :src="userAvatar"
            />
          </view>
          <view class="answer">
            <wd-img
              :width="35" 
              :height="35" 
              :src="robotAvatar"
            />
            <view class="content">
              <view v-if="item.isThinking" class="result" @click="handleTaggleThinkClick(item.message_id)">
                <wd-icon name="caret-down" size="18px" :class="item.thinkExpanded ? 'rotate-270' : ''"></wd-icon>
                <view class="think">已深度思考({{item.thinkDuration || '0.0'}}s)</view>
              </view>
              <ua-markdown :source="item.answer" />
              <view v-if="item.query">
                <view v-if="isChating" class="icon-loading text-20px mt-8px"/>
                <view v-else-if="item.answer" class="tool">
                  <view class="i-iconamoon-copy" style="font-size: 22px;margin-right: 12px;" @click="handleClickMessageTool(item.message_id, 'answer_copy')"></view>
                  <view class="i-iconamoon-synchronize" style="font-size: 22px;margin-right: 12px;" @click="handleClickMessageTool(item.message_id, 'regenerate')"></view>
                  <view class="i-iconamoon-like-fill" v-if="item?.feedback?.rating == 'like'" style="font-size: 22px;margin-right: 12px;" @click="handleClickMessageTool(item.message_id, 'feedback_null')"></view>
                  <view class="i-iconamoon-like" v-else style="font-size: 22px;margin-right: 12px;" @click="handleClickMessageTool(item.message_id, 'feedback_like')"></view>
                  <view class="i-iconamoon-dislike-fill" v-if="item?.feedback?.rating == 'dislike'" style="font-size: 22px;margin-right: 12px;" @click="handleClickMessageTool(item.message_id, 'feedback_null')"></view>
                  <view class="i-iconamoon-dislike" v-else style="font-size: 22px;margin-right: 12px;"  @click="handleClickMessageTool(item.message_id, 'feedback_dislike')"></view>
                </view>
              </view>
              <view v-if="item.knowledgeList" class="knowledge">
                <view class="summary">{{item.knowledgeSummary}}</view>
                <ul>
                  <li v-for="(knowledge, index) in item.knowledgeList" :key="index" @click="handleKnowledgeClick(knowledge)">{{knowledge.name}}</li>
                </ul>
              </view>
              <view v-if="item.suggestionList" class="suggestion">
                <view class="summary">{{item.suggestionSummray}}</view>
                <ul>
                  <li v-for="(suggestion, index) in item.suggestionList" :key="index" @click="handleSuggestionClick(suggestion)">{{suggestion}}</li>
                </ul>
              </view>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
    <view class="footer" :style="`left:${sidebarTranslateX}px`">
      <view class="inputs">
        <wd-input :cursorSpacing="45" ref="chatInput" type="text" v-model="context.query" placeholder="请输入您的问题"  no-border/>
        <wd-icon v-if="isChating" name="stop-circle-filled" size="32px" class="color-#1677ff" @click="handleStopChatMessages"></wd-icon>
        <view v-else class="i-iconamoon-arrow-up-5-circle-fill" style="font-size: 32px" :class="context.query ? 'color-#1677ff' : 'color-#d9d9d9'" @click="handleChatMessages"></view>
      </view>
      <view class="tips">内容由AI生成，请仔细甄别</view>
    </view>
    <view 
      class="mask" 
      ref="maskContainer"
      :style="sidebarTranslateX > 0 ? `left: ${sidebarTranslateX}px; opacity: 1; pointer-events: auto` : ''"
      @click="handleToggleSidebar"
    >
    </view>
    <view :prop="JSON.stringify(context)" :change:prop="render.chatMessage" v-show="false"></view>
    <wd-message-box zIndex="9999"/>
    <wd-toast/>
  </view>
</template>
<script lang="ts">
  import dayjs from 'dayjs';
  import { DOMParser } from 'xmldom';
  import { useSwipe } from '@vueuse/core';
  import { useToast, useMessage } from 'wot-design-uni';
  import { stopChatMessages, listConversations, renameConversations, listMessages, feedbackMessages, getParameters, deleteConversations } from '@/service/chat/dify';
  
  import userAvatar from '@img/user.png';
  import robotAvatar from '@img/robot.png';
  
  const systemInfo = uni.getSystemInfoSync();
  const base = import.meta.env.VITE_BASE_URL;
  
  export default {
    data() {
      return <any>{
        userAvatar: userAvatar,
        robotAvatar: robotAvatar,
        navbarTitle: '新对话',
        thinkTimer: null,
        threshold: 30,
        windowHeight: 0,
        isChating: false,
        context: {
          chat: false,
          base: base,
          query: '',
          taskId: '',
          messageId: '',
          conversationId: '',
          userId: '10000',
          inputs: {
            type: 'chat',
            show_references: '1', 
            show_suggestion: '1', 
          }
        },
        conversationLoading: false,
        conversationHasMore: false,
        conversationDataList: <any>[],
        conversationLimit: 20,
        messageScrollTop: 0,
        messageAutoScroll: true,
        messageLoading: false,
        messageHasMore: false,
        messageDataList: <any>[],
        messageLimit: 20
      };
    },
    setup() {
      const toast = useToast();
      const message = useMessage();
      const sidebarWidth = systemInfo.windowWidth * 0.7;
      const sidebarThreshold = systemInfo.windowWidth * 0.3;
      const messageContainer = ref();
      const maskContainer = ref();
      const sidebarTranslateX = ref(0);
      const sidebarLeftX = ref(-sidebarWidth);
      const sidebarOpen = ref(false);
      const { direction: messageDirection, lengthX: messagelengthX } = useSwipe(messageContainer, {
          threshold: 0,
          onSwipe() {
            if (messageDirection.value === 'right') {
              sidebarTranslateX.value = Math.min(Math.abs(messagelengthX.value), sidebarWidth);
              sidebarLeftX.value = sidebarTranslateX.value - sidebarWidth;
              if (sidebarTranslateX.value >= sidebarThreshold) {
                uni.hideKeyboard()
              }
            }
            if (messageDirection.value === 'left' && sidebarOpen.value) {
              sidebarTranslateX.value = Math.min(sidebarWidth - Math.abs(messagelengthX.value), sidebarWidth);
              sidebarLeftX.value = sidebarTranslateX.value - sidebarWidth;
            }
          },
          onSwipeEnd() {
            if (sidebarTranslateX.value < sidebarThreshold) {
              sidebarOpen.value = false;
              sidebarLeftX.value = 0 - sidebarWidth;
              sidebarTranslateX.value = 0;
            } else {
              sidebarOpen.value = true;
              sidebarLeftX.value = 0;
              sidebarTranslateX.value = sidebarWidth;
            }
          }
      });
      
      const { direction: maskDirection, lengthX : maskLengthX} = useSwipe(maskContainer, {
        threshold: 0,
        onSwipe() {
          if (maskDirection.value === 'left') {
            sidebarTranslateX.value = Math.min(sidebarWidth - Math.abs(maskLengthX.value), sidebarWidth);
            sidebarTranslateX.value = sidebarTranslateX.value < 0 ? 0 :  sidebarTranslateX.value
            sidebarLeftX.value = sidebarTranslateX.value - sidebarWidth;
          }
        },
        onSwipeEnd() {
          if (maskLengthX.value < sidebarThreshold) {
            sidebarOpen.value = true;
            sidebarLeftX.value = 0;
            sidebarTranslateX.value = sidebarWidth;
          } else {
            sidebarOpen.value = false;
            sidebarLeftX.value = 0 - sidebarWidth;
            sidebarTranslateX.value = 0;
          }
        }
      });
      return {
        messageContainer,
        maskContainer,
        toast,
        message,
        sidebarOpen,
        sidebarWidth,
        sidebarThreshold,
        sidebarLeftX,
        sidebarTranslateX
      }
    },
    computed: {
      sidebarStyle() {
        return {
          width: `${this.sidebarWidth}px`,
          left: `${this.sidebarLeftX}px`,
        };
      },
      formatConversateData() {
        const timeGroups:any = {
          '今天': [],
          '昨天': [],
          '7天内': [],
          '30天内': []
        }
        this.conversationDataList.forEach((item:any) => {
          const createTime = dayjs.unix(item.created_at)
          const diffDays = dayjs().diff(createTime, 'day')
          if (diffDays === 0) {
            timeGroups['今天'].push(item)
          } else if (diffDays === 1) {
            timeGroups['昨天'].push(item)
          } else if (diffDays <= 7) {
            timeGroups['7天内'].push(item)
          } else if (diffDays <= 30) {
            timeGroups['30天内'].push(item)
          } else {
            const formattedDate = createTime.format('YYYY-MM-DD')
            if (!timeGroups[formattedDate]) {
              timeGroups[formattedDate] = []
            }
            timeGroups[formattedDate].push(item)
          }
        })
        return Object.entries(timeGroups)
          .map(([timeRange, items]) => ({timeRange,items}))
            .filter((group:any) => group.items.length > 0)
      }
    },
    mounted() {
      if (!this.context.userId) {
        this.toast.show({
          position: 'middle',
          msg: '用户信息不存在'
        });
        return;
      }
      this.handleInitConversation();
      this.loadConversationsData(true);
      uni.getSystemInfo({
        success: (res) => {
          this.windowHeight = res.windowHeight
        }
      })
    },
    methods: {
      handleToggleSidebar() {
        this.sidebarOpen = !this.sidebarOpen;
        if (this.sidebarOpen) {
          this.sidebarLeftX = 0;
          this.sidebarTranslateX = this.sidebarWidth;
        } else {
          this.sidebarLeftX = 0 - this.sidebarWidth;
          this.sidebarTranslateX = 0;
        }
      },
      handleInitConversation() {
        this.messageDataList.length = 0
        this.context.conversationId = ''
        this.context.messageId = ''
        this.context.taskId = ''
        this.context.query = ''
        this.navbarTitle = '新对话'
        getParameters().then((res:any) => {
          const result = res.data
          this.messageDataList.push({
            answer: result.opening_statement,
            suggestionList: result.suggested_questions,
            suggestionSummay: '',
          })
          this.messageLoading = false
        })
      },
      handleNewConversation() {
        if (this.context.conversationId  == '') {
          this.toast.show({
            position: 'middle',
            msg: '已经在新会话中'
          });
          return;
        }
        this.isChating && this.handleStopChatMessages()
        this.handleInitConversation()
      },
      handleSelectConversation(item: any) {
        this.handleToggleSidebar();
        this.context.conversationId = item.id;
        this.navbarTitle = item.name;
        this.loadMessagesData(true);
      },
      handleRenameConversation(item: any) {
        this.message
          .prompt({
            title: '请输入会话名称',
            inputValue: item.name
          })
          .then((resp:any) => {
            if ('confirm' == resp.action && resp.value) {
              const data = {
                userId: this.context.userId,
                name: resp.value,
                autoGenerate: false,
              }
              renameConversations(item.id, data).then(() => {
                const filterIndex = this.conversationDataList.findIndex((item:any) => item.id === item.id);
                if (filterIndex > -1) {
                  item.name = resp.value;
                  this.conversationDataList.splice(filterIndex, 1, item);
                }
              }).catch((err:any) => {
                console.error(err)
              })
            }
            console.log(resp)
          })
          .catch((error:any) => {
            console.log(error)
          })
      },
      handleDeleteConversation(item: any) {
        deleteConversations(item.id, {userId: this.context.userId}).then(() => {
          this.loadConversationsData(true)
        }).catch((err:any) => {
          console.error(err)
        })
      },
      handleChatMessages() {
        if (!this.context.query) {
          return;
        }
        if (!this.context.userId) {
          this.toast.show({
            position: 'middle',
            msg: '用户信息不存在'
          });
          return;
        }
        this.context.chat = true;
        this.isChating = true;
        this.messageDataList.push({query: this.context.query, answer: ''});
        this.handleScrollMessageToBottom();
      },
      handleChatOnMessage(chunk:any) {
        const event = chunk?.event || ''
        if (event === 'error') {
          this.isChating = false
          this.context.messageId = ''
          this.context.taskId = ''
          this.context.query = ''
          this.context.inputs.type = 'chat'
          this.messageDataList[this.messageDataList.length - 1]['answer'] = '请求异常'
          return
        }
        if (event === 'workflow_started') {
          if (!this.context.messageId) {
            this.context.conversationId = chunk.conversation_id
            this.context.messageId = chunk.message_id
            this.context.taskId = chunk.task_id
            this.context.query = ''
          }
          this.messageDataList[this.messageDataList.length - 1]['message_id'] = chunk.message_id
          const filterConversationIndex = this.conversationDataList.findIndex(
            (item: any) => item.id === chunk.conversation_id)
          if (filterConversationIndex === -1) {
            this.conversationDataList.unshift({
              id: chunk.conversation_id,
              created_at: chunk.created_at,
              name: '新对话',
              isNew: true
            })
          }
        }
        if (event === 'message_end') {
          this.isChating = false
          this.context.messageId = ''
          this.context.taskId = ''
          this.context.chat = false
          this.context.inputs.type = 'chat'
          const filterConversationIndex = this.conversationDataList.findIndex(
            (item: any) => item.id === chunk.conversation_id && item.isNew)
          if (filterConversationIndex > -1) {
            const data = {
              userId: this.context.userId,
              name: this.messageDataList.find((message:any) => message.query)?.query,
              autoGenerate: false,
            }
            renameConversations(chunk.conversation_id, data).then(() => {
              this.conversationDataList[filterConversationIndex].name  = data.name
            }).catch((err:any) => {
              console.error(err)
            })
          }
        }
        if (event == 'message') {
          let message = chunk?.answer || '';
          if (this.context.inputs.type === 'chat') {
            if (message.indexOf('<div class=\"knowledge\">') > -1) {
              const { knowledgeSummary, knowledgeList } = this.handleFormatKnowledge(message);
              this.messageDataList[this.messageDataList.length - 1]['knowledgeSummary'] = knowledgeSummary;
              this.messageDataList[this.messageDataList.length - 1]['knowledgeList'] = knowledgeList;
              this.handleScrollMessageToBottom()
              return
            }
            if (message.indexOf('<div class=\"suggestion\">') > -1) {
              const { suggestionSummray, suggestionList } = this.handleFormatSuggestion(message);
              this.messageDataList[this.messageDataList.length - 1]['suggestionSummray'] = suggestionSummray;
              this.messageDataList[this.messageDataList.length - 1]['suggestionList'] = suggestionList;
              this.handleScrollMessageToBottom()
              return
            }
            if (message.indexOf('<think>') > -1) {
              message = message.replace('<think>', '<div class="think">')
              this.messageDataList[this.messageDataList.length - 1]['isThinking'] = true
              this.thinkTimer = setInterval(() => {
                const thinkDuration = this.messageDataList[this.messageDataList.length - 1]['thinkDuration'] || 0
                this.messageDataList[this.messageDataList.length - 1]['thinkDuration'] = Math.round((thinkDuration + 0.1) * 10) / 10 
              }, 100)
            }
            if (message.indexOf('</think>') > -1) {
              message = message.replace('</think>', '</div>')
              this.handleTaggleThinkClick(chunk.message_id)
              clearInterval(this.thinkTimer)
            }
          }
          if (message) {
            this.messageDataList[this.messageDataList.length - 1]['answer'] += message
            this.handleScrollMessageToBottom()
          }
        }
      },
      handleStopChatMessages() {
        stopChatMessages(this.context.taskId, {userId: this.context.userId}).then(() => {
          clearInterval(this.thinkTimer)
          this.isChating = false
          this.context.messageId = ''
          this.context.taskId = ''
          this.context.inputs.type = 'chat'
        }).catch((err:any) => {
          console.error(err)
        })
      },
      handleTaggleThinkClick(messageId: string) {
       this.messageDataList.forEach((message:any) => {
          if (message.message_id === messageId) {
            message.thinkExpanded = !message.thinkExpanded
            message.answer = message.thinkExpanded 
              ? message.answer.replace('<div class="think">', '<div class="think hidden">')
              : message.answer.replace('<div class="think hidden">', '<div class="think">')
          }
        })
      },
      handleKnowledgeClick (knowledge:any) {
        if (knowledge) {
          this.context.query = knowledge.name
          this.context.inputs = {
            type: 'document',
            document_id: knowledge.document_id,
            dataset_id: knowledge.dataset_id
          }
          this.handleChatMessages()
        }
      },
      handleSuggestionClick (suggestion:string) {
        if (suggestion) {
          this.context.query = suggestion
          this.handleChatMessages()
        }
      },
      handleClickMessageTool(messageId: string, key: string) {
        switch (key) {
          case 'query_copy':
            this.handleCopyQuery(messageId)
            break;
          case 'answer_copy':
            this.handleCopyAnswer(messageId)
            break;
          case 'regenerate':
            this.handleRegenerateMessage(messageId)
            break;
          case 'feedback_like':
            this.handleFeedbackMessage(messageId, 'like')
            break;
          case 'feedback_dislike':
            this.handleFeedbackMessage(messageId, 'dislike')
            break;
          case 'feedback_null':
            this.handleFeedbackMessage(messageId, null)
            break;
        }
      },
      handleCopyQuery(messageId: string) {
        const filterMessage = this.messageDataList.find(
          (message:any) => message.message_id == messageId)
        if (filterMessage && filterMessage.query) {
          const content = filterMessage.query;
          uni.setClipboardData({
            data: content.substring(content.indexOf('</div>') + 6),
            success: function() {},
            fail: function(err) {
              console.error(err);
            }
          });
        }
      },
      handleCopyAnswer(messageId: string) {
        const filterMessage = this.messageDataList.find(
          (message:any) => message.message_id == messageId)
        if (filterMessage && filterMessage.answer) {
          const content = filterMessage.answer
          uni.setClipboardData({
            data: content.substring(content.indexOf('</div>') + 6),
            success: function() {},
            fail: function(err) {
              console.error(err);
            }
          });
        }
      },
      handleRegenerateMessage(messageId: string) {
        const filterMessage = this.messageDataList.find(
          (message:any) => message.message_id == messageId)
        if (filterMessage && filterMessage.query) {
          this.context.query = filterMessage.query
          this.handleChatMessages()
        }
      },
      handleFeedbackMessage(messageId: string, rating: string | null) {
        const data = {
          userId: this.context.userId,
          rating: rating,
          content: ''
        }
        feedbackMessages(messageId, data).then((res:any) =>{
          if (res.data && res.data.result == 'success') {
            const filterIndex = this.messageDataList.findIndex(
              (message:any) => message.message_id === messageId)
            if (filterIndex > -1) {
              this.messageDataList[filterIndex].feedback = {rating}
            }
          }
        }).catch((err:any) => {
          console.log(err)
        })
      },
      loadConversationsData(reload:boolean = false) {
        if (this.conversationLoading) {
          return
        }
        this.conversationLoading = true
        if (reload == true) {
          this.conversationDataList = []
          this.conversationHasMore = true
        }
        if (!this.conversationHasMore) {
          this.conversationLoading = false
          return
        }
        const params = {
          userId: this.context.userId,
          lastId: this.conversationDataList.length ? this.conversationDataList[this.conversationDatalist.length - 1].id : undefined,
          limit: this.conversationLimit
        }
        listConversations(params).then((res:any) => {
          const result = res.data
          if (result) {
            this.conversationDataList = this.conversationDataList.concat(result.data)
            this.conversationHasMore = result.has_more
          }
        }).catch((err:any) => {
          console.log(err);
        }).finally(() => {
          this.conversationLoading = false
        })
      },
      loadMessagesData(reload:boolean = false) {
        if (this.messageLoading) {
          return;
        }
        this.messageLoading = true;
        if (reload) {
          this.messageDataList.length = 0;
          this.messageHasMore = true;
        }
        if (!this.messageHasMore) {
          this.messageLoading = false;
          return;
        }
        const params = {
          userId: this.context.userId,
          conversationId: this.context.conversationId,
          firstId: this.messageDataList.length ? this.messageDataList[this.messageDataList.length - 1].id : undefined,
          limit: this.messageLimit
        }
        listMessages(params).then((res:any) => {
          const result = res.data;
          if (result) {
            result.data.forEach((item:any) => {
              item = this.handleFormatAnswer(item);
            })
            const newMessageList = result.data.reverse();
            this.messageDataList = newMessageList.concat(this.messageDataList);
            this.messageHasMore = result.has_more;
            nextTick(() => {
              if (reload) {
                this.handleScrollMessageToBottom();
              }
            })
          }
        }).catch((err:any) => {
          console.error(err);
        }).finally(() => {
          this.messageLoading = false;
        })
      },
      handleFormatAnswer(answer:any) {
        if (answer) {
          let message = answer.answer || '';
          let suggestionIndex = message.indexOf('<div class=\"suggestion\">');
          let knowledgeIndex = message.indexOf('<div class=\"knowledge\">');
          let thinkIndex = message.indexOf('<think>');
          if (suggestionIndex > -1) {
            const { suggestionSummray, suggestionList } = this.handleFormatSuggestion(message.substring(suggestionIndex));
            answer.suggestionSummray = suggestionSummray;
            answer.suggestionList = suggestionList;
          }
          if (knowledgeIndex > -1) {
            const { knowledgeSummary, knowledgeList } = this.handleFormatKnowledge(message.substring(knowledgeIndex, suggestionIndex));
            answer.knowledgeSummary = knowledgeSummary;
            answer.knowledgeList = knowledgeList;
          }
          answer.message_id = answer.id;
          answer.answer = knowledgeIndex > -1 ? message.substring(0,  knowledgeIndex) : suggestionIndex > -1 ? message.substring(0,  suggestionIndex) : message;
          if (thinkIndex > -1) {
            answer.isThinking = true;
            answer.answer = answer.answer.replace('<think>', '<div class="think">').replace('</think>', '</div>');
          }
        }
        return answer
      },
      handleFormatKnowledge(knowledgeStr:string) {
        const parser = new DOMParser();
        const knowledge = parser.parseFromString(knowledgeStr, 'text/html');
        const knowledgeCount = knowledge.documentElement.getElementsByTagName('div')[0]?.textContent;
        const knowledgeSummary = `知识库检索到的参考数据：共${knowledgeCount}个`;
        const listItems = knowledge.documentElement.getElementsByTagName('li');
        const knowledgeList = <any>[];
        for (let i = 0; i < listItems.length; i++) {
          const item = listItems[i];
          const elements = item.getElementsByTagName('div');
          knowledgeList.push({
            name: elements[0]?.textContent?.trim(),
            document_id: elements[1]?.textContent,
            dataset_id: elements[2]?.textContent
          })
        };
        return {knowledgeSummary, knowledgeList};
      },
      handleFormatSuggestion(suggestionStr:string) {
        const parser = new DOMParser();
        const suggestion = parser.parseFromString(suggestionStr, 'text/html');
        const suggestionSummray = suggestion.documentElement.getElementsByTagName('div')[0]?.textContent;
        const listItems = suggestion.documentElement.getElementsByTagName('li');
        const suggestionList = <any>[];
        for (let i = 0; i < listItems.length; i++) {
          const item = listItems[i];
          const elements = item.getElementsByTagName('div');
          suggestionList.push(elements[0]?.textContent?.trim());
        }
        return {suggestionSummray, suggestionList};
      },
      handleMessageScroll(e:any) {
        const { scrollTop: currentScrollTop, scrollHeight: currentScrollHeight } = e.detail;
        this.messageAutoScroll = currentScrollTop >= (currentScrollHeight - this.windowHeight - this.threshold)
      },
      handleScrollMessageToBottom() {
        nextTick(() => {
          if (this.messageAutoScroll) {
            uni.createSelectorQuery()
              .select('#messageScrollList')
              .fields({
                size: true,
                scrollOffset: true
              }, (res:any) => {
                this.messageScrollTop = res.scrollHeight
              })
              .exec();
          }
        })
      }
    },
  }
</script>
<script module="render" lang="renderjs">
  import { fetchEventSource } from '@microsoft/fetch-event-source';
  export default {
    data() {
      return {
        retryCount: 0,
        maxRetries: 3
      };
    },
    methods: {
      chatMessage(newValueStr, oldValueStr, ownerVm){
        const context = JSON.parse(newValueStr)
        if (!context.chat || !context.query) {
          return false
        }
        try {
          const controller = new AbortController()
          const data = {
            query: context.query,
            userId: context.userId,
            inputs: context.inputs,
            conversationId: context.conversationId,
          }
          fetchEventSource(context.base + "/chat/dify/chat-messages", {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'responseType': 'text/event-stream'
            },
            signal: controller.signal,
            body: JSON.stringify(data),
            openWhenHidden: true,
            onmessage(event) {
              const chunk = JSON.parse(event.data)
              ownerVm.callMethod('handleChatOnMessage', chunk)
            },
            onclose() {
              console.log('close')
              controller.abort()
            },
            onerror(error) {
              console.log('error', error)
              this.retryCount++
              if (this.retryCount > this.maxRetries) {
                this.retryCount = 0
                controller.abort()
                throw error
              }
            }
          });
        } catch (error) {
          console.error('请求错误!', error)
        }
      }
    }
  }
</script>
<style scoped lang="scss">
  .wrapper {
    display: flex;
    flex-direction: column;
    justify-content: start;
    height: 100vh;
    width: 100%;
    .mask {
      height: 100vh;
      width: 100%;
      overflow-x: hidden;
      position: absolute;
      background: rgba(0, 0, 0, 0.3);
      z-index: 90;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.3s ease-out;
    }
    .message {
      flex: 1;
      padding: 12px;
      position: relative;
      overflow-y: scroll;
      overflow-x: hidden;
      .list {
        height: 100%;
        .query {
          display: flex;
          text-align: right;
          justify-content: right;
          align-items: center;
          margin-bottom: 16px;
          .tool {
            display: flex;
            font-size: 20px;
            color: #909090;
          }
          .content {
            text-align: justify;
            background-color: #1677ff0f;
            border-radius: 8px 2px 8px 8px;
            color: #262626;
            margin-right: 8px;
            padding: 6px 8px;
          }
        }
        .answer {
          display: flex;
          margin-bottom: 16px;
          .content {
            text-align: justify;
            background-color: #ffffff;
            color: #262626;
            padding: 0 12px;
            flex: 1;
            .result {
              display: inline-flex;
              align-items: center;
              background: #f5f5f5;
              border-radius: 8px;
              padding: 4px 8px;
              font-size: 13px;
              margin: 8px 0;
            }
            .tool {
              display: flex;
              margin-top: 8px;
              font-size: 20px;
              color: #909090;
            }
            :deep(.knowledge) {
              margin: 24px 0;
              ul {
                display: flex;
                flex-direction: column;
                align-items: flex-start;
                list-style: decimal;
                list-style-position: inside;
                margin: 0;
                padding: 0;
                li {
                  margin-top: 8px;
                  color: #1677ff;
                  font-size: 15px;
                  border-bottom: 1px dashed;
                }
              }
            }
            :deep(.suggestion) {
              margin: 8px 0 16px;
              ul {
                display: flex;
                flex-direction: column;
                align-items: flex-start;
                margin: 0;
                padding: 0;
                li {
                  background: #1677ff0f;
                  display: inline-flex;
                  padding: 2px 8px;
                  border-radius: 8px;
                  cursor: pointer;
                  color: #1677ff;
                  margin-top: 8px;
                  font-size: 14px;
                }
              }
            }
          }
        }
      }
    }
    .footer {
      position: relative;
      .inputs {
       display: flex;
       justify-content: space-between;
       align-items: center;
       border-radius: 24px;
       padding: 10px 16px;
       width: calc(100% - 32px);
       background-color: #f3f4f6;
       box-shadow: 0px 0px 0px 0.5px #dce0e9;
       box-sizing: border-box;
       margin: 16px auto 8px;
        .wd-input {
          background: none;
          flex: 1;
        }
      }
      .tips {
        text-align: center;
        width: 100%;
        margin-bottom: 4px;
        font-size: 12px;
        color: #a3a3a3;
      }
    }
    .wd-sidebar {
      z-index: 80;
      position: absolute;
      transition: transform 0.3s ease-out;
      .list {
        padding-top: 25px;
        height: calc(100vh - 25px);
        overflow-y: scroll;
        overflow-x: hidden;
        .item-group {
          padding: 16px 8px;
          .item-date {
            color: rgba(0, 0, 0, 0.6);
            text-align: left;
            font-weight: 400;
            line-height: 18px;
            position: sticky;
            top: 0;
            padding: 0 8px;
            margin-bottom: 8px;
            background: #ffffff;
            font-size: 14px;
            z-index: 99;
          }
          .wd-sidebar-item {
            justify-content: start;
            align-items: center;
            white-space: nowrap;
            overflow: hidden;
            color: rgba(0, 0, 0, 0.9);
            font-weight: 400;
            height: 38px;
            display: flex;
            border-radius: 12px;
            display: flex;
            align-items: center;
            position: relative;
            padding: 0 8px;
            font-size: 15px;
            &.active {
              background-color: #1677ff0f;
            }
            .name {
              flex: 1;
              min-width: 0;
              overflow: hidden;
            }
            .tool {
              height: 24px;
              position: absolute;
              top: 50%;
              right: 8px;
              transform: translateY(-50%);
              z-index: 9;
              display: flex;
              align-items: center;
            }
            .sw {
              content: "";
              pointer-events: none;
              border-top-right-radius: 12px;
              border-bottom-right-radius: 12px;
              position: absolute;
              top: 0;
              bottom: 0;
              right: 0;
              width: 104px;
              background: linear-gradient(90deg, rgba(249, 251, 255, 0) 0%, #ffffff 50%, #ffffff 100%);
            }
          }
        }
      }
    }
  }
  .icon-chat-add {
    display: inline-block;
    width: 1em;
    height: 1em;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M12.5 2.012A11 11 0 0 0 12 2C6.478 2 2 6.284 2 11.567c0 2.538 1.033 4.845 2.719 6.556c.371.377.619.892.519 1.422a5.3 5.3 0 0 1-1.087 2.348a6.5 6.5 0 0 0 4.224-.657c.454-.241.681-.362.842-.386s.39.018.848.104c.638.12 1.286.18 1.935.18c5.522 0 10-4.284 10-9.567q0-.286-.017-.567M15 5.5h7M18.5 2v7m-6.504 3h.008m3.987 0H16m-8 0h.009' color='%23000'/%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }
  .icon-loading {
    display: inline-block;
    width: 1em;
    height: 1em;
    --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2Zm0 18a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z' opacity='0.5'/%3E%3Cpath fill='%23000' d='M20 12h2A10 10 0 0 0 12 2V4A8 8 0 0 1 20 12Z'%3E%3CanimateTransform attributeName='transform' dur='1s' from='0 12 12' repeatCount='indefinite' to='360 12 12' type='rotate'/%3E%3C/path%3E%3C/svg%3E");
    background-color: currentColor;
    -webkit-mask-image: var(--svg);
    mask-image: var(--svg);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
  }
  :deep(.ua__markdown) {
    p {
      margin-top: 0 !important;
      margin-bottom: 0 !important;
    }
    .think {
      color: #8b8b8b;
      margin: 0;
      padding: 0 0 0 12px;
      position: relative;
      border-left: 2px solid #e5e5e5;
    }
  }
</style>
